//ComplexNumber.java
//Timothy Grant
//CS1410
//Calculator 1410
//Final Version

/*
 * Use of brackets instead of parentheses to enclose ordered pair.
 * Can use either 'i' or 'j' to denote imaginary part.
 */
package numbers;

import java.util.Arrays;
import java.util.List;

public class ComplexNumber implements Number {
    //instance fields

    private double real;
    private double imag;
    private static String[] validOps = {"+", "-", "*", "/", "%"};
    private static String[] hiOps = {"*", "/", "%"};

    //begin set constructor
    public ComplexNumber(double r, double i) {
        real = r;
        imag = i;
    }

    public ComplexNumber(String rs, String is) {
        real = Double.parseDouble(rs);
        imag = Double.parseDouble(is);
    }

    //add()
    public ComplexNumber add(ComplexNumber rightOpand) {
        return new ComplexNumber(
                this.real + rightOpand.real, this.imag + rightOpand.imag);
    }//end add method

    //subract()
    public ComplexNumber subtract(ComplexNumber rightOpand) {
        return new ComplexNumber(
                this.real - rightOpand.real, this.imag - rightOpand.imag);
    }//end subtract method

    //multiply()
    public ComplexNumber multiply(ComplexNumber rightOpand) {
        return new ComplexNumber(
                (this.real * rightOpand.real) - (this.imag * rightOpand.imag),
                (this.real * rightOpand.imag) + (this.imag * rightOpand.real));
    }//end multiply method

    //divide()
    public ComplexNumber divide(ComplexNumber rightOpand) {
        if (rightOpand.real == 0) {
            throw new ArithmeticException("Cannot divide by zero.");
        }
        return new ComplexNumber(
                (this.real * rightOpand.real + this.imag * rightOpand.imag) / (Math.pow(rightOpand.real, 2) + Math.pow(rightOpand.imag, 2)),
                (this.imag * rightOpand.real - this.real * rightOpand.imag) / (Math.pow(rightOpand.real, 2) + Math.pow(rightOpand.imag, 2)));
    }//end divide method

    //equals()
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ComplexNumber) {
            ComplexNumber temp = (ComplexNumber) obj;
            return this.real == temp.real && this.imag == temp.imag;
        }
        return false;
    }//end equals method

    //toString()
    @Override
    public String toString() {
        return "[" + this.real + "+" + this.imag + "i]";
    }//end toString method

    //begin isValidOperator()
    public boolean isValidOperator(String s) {
        for (String x : validOps) {
            if (s.equals(x)) {
                return true;
            }
        }
        return false;
    }//end isValidOperator()

    //begin opand2Str()
    public String opand2Str(String e, int i) {
        int strIndex = i;
        char nextCh;
        String realStr;
        String retStr = ("");

        try {
            nextCh = e.charAt(strIndex);
            ++strIndex;
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        if (nextCh != '[') {
            throw new ArithmeticException("Invalid Complex Number");
        }

        try {
            retStr += nextCh;
            realStr = new RealNumber(0).opand2Str(e, strIndex);
            strIndex += realStr.length();
            retStr += realStr;
            nextCh = e.charAt(strIndex);
            ++strIndex;
        } catch (ArithmeticException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        if (nextCh == 'i' || nextCh == 'j') {
            try {
                retStr += nextCh;
                nextCh = e.charAt(strIndex);
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException("Invalid Complex Number");
            }

            if (nextCh != ']') {
                throw new ArithmeticException("Invalid Complex Number");
            } else {
                retStr += nextCh;
                return retStr;
            }
        }

        if (nextCh == ']') {
            retStr += nextCh;
            return retStr;
        }

        while (Character.isWhitespace(nextCh)) {

            try {
                retStr += nextCh;
                nextCh = e.charAt(strIndex);
                ++strIndex;
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException("Invalid Complex Number");
            }

        }

        if (!(nextCh == '+' || nextCh == '-')) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        try {
            retStr += nextCh;
            nextCh = e.charAt(strIndex);
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        while (Character.isWhitespace(nextCh)) {
            try {
                retStr += nextCh;
                ++strIndex;
                nextCh = e.charAt(strIndex);
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException("Invalid Complex Number");
            }

        }

        try {
            realStr = new RealNumber(0).opand2Str(e, strIndex);
            retStr += realStr;
            strIndex += realStr.length();
            nextCh = e.charAt(strIndex);
            ++strIndex;
        } catch (ArithmeticException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        if (nextCh != 'i' && nextCh != 'j') {
            throw new ArithmeticException("Invalid Complex Number");
        }

        try {
            retStr += nextCh;
            nextCh = e.charAt(strIndex);
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid Complex Number");
        }

        if (nextCh != ']') {
            throw new ArithmeticException("Invalid Complex Number");
        }

        retStr += nextCh;

        return retStr;
    }//end opand2Str method

    //begin op2Str()
    public String op2Str(String e, int i) {
        int strIndex = i;
        char nextCh;
        String retStr = ("");

        try {
            nextCh = e.charAt(strIndex);
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException("Invalid operation" + ex.getMessage());
        }

        if (isValidOperator(Character.toString(nextCh))) {
            retStr += nextCh;
            return retStr;
        } else if (!isValidOperator(Character.toString(nextCh))) {
            throw new ArithmeticException("Invalid operation");
        }

        return retStr;
    }//end op2Str()

    //begin str2Number()
    public Number str2Number(String s) {
        String rs;
        String is;
        char nextCh;
        int strIndex;
        String tempStr;

        is = "";
        strIndex = 0;

        try {
            nextCh = s.charAt(strIndex);
            ++strIndex;
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        if (nextCh != '[') {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        try {
            rs = new RealNumber(0).opand2Str(s, strIndex);
            strIndex += rs.length();
        } catch (ArithmeticException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        try {
            nextCh = s.charAt(strIndex);
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        if (nextCh == 'j' || nextCh == 'i') {
            try {
                ++strIndex;
                nextCh = s.charAt(strIndex);
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException(s + " is an invalid complex number");
            }

            if (nextCh == ']') {
                return new ComplexNumber("0", rs);
            } else {
                throw new ArithmeticException(s + " is an invalid complex number");
            }

        }

        if (nextCh == ']') {
            return new ComplexNumber(rs, "0");
        }

        while (Character.isWhitespace(nextCh)) {
            try {
                ++strIndex;
                nextCh = s.charAt(strIndex);
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException(s + " is an invalid complex number");
            }
        }

        if (!(nextCh == '+' || nextCh == '-')) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        is += nextCh;

        try {
            ++strIndex;
            nextCh = s.charAt(strIndex);
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        while (Character.isWhitespace(nextCh)) {
            try {
                ++strIndex;
                nextCh = s.charAt(strIndex);
            } catch (IndexOutOfBoundsException ex) {
                throw new ArithmeticException(s + " is an invalid complex number");
            }
        }

        try {
            tempStr = new RealNumber(0).opand2Str(s, strIndex);
            strIndex += tempStr.length();
            is += tempStr;
            nextCh = s.charAt(strIndex);
            ++strIndex;
        } catch (ArithmeticException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        } catch (IndexOutOfBoundsException ex) {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        if (nextCh != 'j' && nextCh != 'i') {
            throw new ArithmeticException(s + " is an invalid complex number");
        }

        nextCh = s.charAt(strIndex);

        if (nextCh != ']') {
            throw new ArithmeticException(s + " is an invalid complex number");
        }
        return new ComplexNumber(rs, is);
    }//end str2Number method

    //begin doOp()
    public Number doOp(Number n1, Number n2, String op) {
        ComplexNumber result = new ComplexNumber(0, 0);
        ComplexNumber num1;
        ComplexNumber num2;
        //System.out.printf("n1= %s, n2= %s%n", n1.toString(), n2.toString());
        try {
            num1 = (ComplexNumber) n1;
            num2 = (ComplexNumber) n2;
            //result = null;
        } catch (ClassCastException cce) {
            throw new ArithmeticException("Invalid Operation" + cce.getMessage());
        }
        if (op.equals("+")) {
            try {
                result = num1.add(num2);
            } catch (ArithmeticException ex) {
                throw new ArithmeticException("Invalid Operation" + ex.getMessage());
            }
        } else if (op.equals("-")) {
            try {
                result = num1.subtract(num2);
            } catch (ArithmeticException ex) {
                throw new ArithmeticException("Invalid Operation" + ex.getMessage());
            }
        } else if (op.equals("*")) {
            try {
                result = num1.multiply(num2);
            } catch (ArithmeticException ex) {
                throw new ArithmeticException("Invalid Operation" + ex.getMessage());
            }
        } else if (op.equals("/")) {
            try {
                result = num1.divide(num2);
            } catch (ArithmeticException ex) {
                throw new ArithmeticException("Invalid Operation" + ex.getMessage());
            }
        } else {
            throw new ArithmeticException("Invalid Operation");
        }
        return result;
    }//end doOp()

    //begin isLower()
    public boolean isLower(String op1, String op2) {
        List<String> hiOpsList = Arrays.asList(hiOps);
        if (hiOpsList.contains(op1) && hiOpsList.contains(op2)) {
            return false;
        } else if (!hiOpsList.contains(op1) && !hiOpsList.contains(op2)) {
            return false;
        } else if (hiOpsList.contains(op1) && !hiOpsList.contains(op2)) {
            return false;
        }
        return true;
    }//end isLower()
}//end Class ComplexNumber

